/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Gtk;
using GDK=Gdk;
using Glade;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;

namespace Galaxium.Protocol.Aim.GtkGui
{
	public class AimSessionWidget : ISessionWidget<Widget>
	{
		public event EventHandler<ChatEventArgs> ActivateChatWidget;
		
		[Widget ("txtDisplayName")]
		private Entry _txtDisplayName;
		[Widget ("imgDisplayImage")]
		private Image _imgDisplayImage;
		[Widget ("txtMessage")]
		private Entry _txtMessage;
		[Widget ("txtSearch")]
		private Entry _txtSearch;
		[Widget ("btnAddContact")]
		private Button _btnAddContact;
		[Widget ("boxInfo")]
		private Table _boxInfo;
		[Widget ("boxList")]
		private VBox _boxList;
		[Widget ("boxSearch")]
		private VBox _boxSearch;
		
		private Widget _nativeWidget;
		private IControllerWindow<Widget> _window;
		private ImageComboBox<IPresence> _cboStatus;
		private ContactTreeView _treeView;
		private AimSession _session;
		private bool _isInitialized;
		
		private uint _offlineContactsNode;
		private uint _onlineContactsNode;
		
		public IControllerWindow<Widget> Window
		{
			get { return _window; }
		}
		
		public ISession Session
		{
			get { return _session; }
		}

		public IAccount Account
		{
			get { return _session.Account; }
		}

		public Widget NativeWidget
		{
			get { return _nativeWidget; }
		}
		
		// ================================================================== //
		// CONSTRUCTOR/DESTRUCTOR
		// ================================================================== //
		
		internal AimSessionWidget (IControllerWindow<Widget> window, AimSession session)
		{
			AimWidgetUtility.AddSessionWidget(session, this);
			
			_window = window;
			_session = session;
			
			session.ContactAdded += SessionContactAdded;
			session.ContactReverse += SessionContactReverse;
			session.ContactRemoved += SessionContactRemoved;
			session.GroupAdded += SessionGroupAdded;
			session.GroupRemoved += SessionGroupRemoved;
			session.GroupRenamed += SessionGroupRenamed;
			session.ContactChanged += SessionContactChanged;
			session.ContactPresenceChanged += SessionContactPresenceChanged;
			session.ContactDisplayMessageChanged += SessionContactDisplayMessageChanged;
			session.ContactDisplayImageChanged += SessionContactDisplayImageChanged;
			
			session.Account.DisplaySettingsChange += DisplaySettingsChange;
			session.Account.DisplayImageChange += DisplayImageChange;
		}
		
		// ================================================================== //
		// PUBLIC FUNCTIONS
		// ================================================================== //
		
		public void Initialize ()
		{
			if (!_isInitialized)
			{
				XML.CustomHandler = new XMLCustomWidgetHandler (CreateCustomWidget);
				_nativeWidget = GladeUtility.ExtractWidget<Widget> (GladeUtility.GetGladeResourceStream (typeof (AimSessionWidget).Assembly, "SessionWidget.glade"), "widget", this);
				
				_btnAddContact.Image = new Image (IconUtility.GetIcon ("galaxium-add-contact", IconSizes.Small));
				
				//SetAccountImage ();
				
				_treeView = new ContactTreeView ();
				_treeView.RowActivated += new RowActivatedHandler(TreeRowActivated);
				_treeView.ButtonPressEvent += new ButtonPressEventHandler(TreeButtonPressed);
				
				ScrolledWindow scrolledWindow = new ScrolledWindow ();
				scrolledWindow.HscrollbarPolicy = PolicyType.Automatic;
				scrolledWindow.VscrollbarPolicy = PolicyType.Automatic;
				scrolledWindow.Add (_treeView);
				scrolledWindow.ShadowType = ShadowType.In;
				
				_boxList.PackEnd (scrolledWindow, true, true, 0);
				
				_nativeWidget.ShowAll ();
				
				AimAccount account = Session.Account as AimAccount;
				_txtDisplayName.Text = account.DisplayName;
				_txtDisplayName.FocusOutEvent += DisplayNameLeaveEvent;
				_txtDisplayName.KeyPressEvent += DisplayNameSetEvent;
				
				_boxSearch.Hide();
				
				_isInitialized = true;
				
				RebuildTree (); //this makes sure the virtual groups are initialized
			}
		}
		
		public void Update ()
		{
			
		}
		
		public void SwitchTo ()
		{
			
		}
		
		public void ShowSearchBar (bool show)
		{
			_boxSearch.Visible = show;
			
			if (show == false)
				_txtSearch.Text = String.Empty;
		}
		
		// ================================================================== //
		// PRIVATE FUNCTIONS
		// ================================================================== //
		
		private Widget CreateCustomWidget (XML gxml, string func_name, string name, string string1, string string2, int int1, int int2)
		{
			if (name == "cboStatusPlaceHolder")
			{
				_cboStatus = new ImageComboBox<IPresence> (
					new ImageComboTextLookup<IPresence> (PresenceTextLookup),
					new ImageComboPixbufLookup<IPresence> (PresenceImageLookup)
				);
				
				_cboStatus.Append (AimPresence.Online);
				_cboStatus.Append (AimPresence.FreeForChat);
				_cboStatus.Append (AimPresence.Away);
				_cboStatus.Append (AimPresence.Dnd);
				_cboStatus.Append (AimPresence.Na);
				_cboStatus.Append (AimPresence.Occupied);
				_cboStatus.Append (AimPresence.Invisible);
				
				_cboStatus.Select (Session.Account.Presence);
				
				_cboStatus.Changed += StatusChangedEvent;
				
				return _cboStatus;
			}
			
			return new Label ("ERROR");
		}
		
		private void DisplayImageChange (object sender, EventArgs args)
		{
		
				//SetAccountImage ();
		}
		
		private void RefreshTreeView ()
		{
			Console.WriteLine ("$$$$$$$$$$$ REFRESH");
			_treeView.Refilter ();
			_treeView.ExpandAll ();
			_treeView.QueueDraw ();
		}
		
		private void RebuildTree ()
		{
			Console.WriteLine ("$$$$$$$$$$$ REBUILD");
			_treeView.Clear ();
			
			AimSession session = Session as AimSession;
			AimAccount account = session.Account as AimAccount;
			
			if (account.ViewByGroup)
			{
				AimGroup offlineContacts = new AimGroup (session, "offline", "Offline Contacts", true);
				
				foreach (AimGroup group in session.Groups)
				{
					if (account.ShowEmptyGroups || group.OnlineCount > 0)
						_treeView.AddNode (group);
					
					if (account.GroupOfflineContacts)
					{
						foreach (AimContact contact in group)
						{
							if (contact.Presence == AimPresence.Offline)
								offlineContacts.Add (contact); //we don't need to check for duplicates, this is already done
						}
					}
				}
				
				if (account.GroupOfflineContacts)
					_offlineContactsNode = _treeView.AddNode (offlineContacts);
			}
			else
			{
				//ViewByStatus
				AimGroup onlineContacts = new AimGroup (session, "online", "Online Contacts", true);
				AimGroup offlineContacts = new AimGroup (session, "offline", "Offline Contacts", true);

				foreach (AimGroup group in session.Groups)
				{
					foreach (AimContact contact in group)
					{
						if (contact.Presence == AimPresence.Offline)
							offlineContacts.Add (contact);
						else
							onlineContacts.Add (contact);
					}
				}
				
				_onlineContactsNode = _treeView.AddNode (onlineContacts);
				_offlineContactsNode = _treeView.AddNode (offlineContacts);
			}
			
			_treeView.ExpandAll ();
		}
		
		private void DisplaySettingsChange (object sender, PropertyEventArgs args)
		{
		
				switch (args.Property) {
				case "ViewByGroup":
				case "ShowEmptyGroups":
				case "GroupOfflineContacts":
					RebuildTree ();
					break;
				default:
					RefreshTreeView ();
					break;
				}
				
		}
		
		private void SessionContactChanged (object sender, ContactEventArgs args)
		{
			Console.WriteLine ("$$$$$$$$$$$ CONTACT CHANGE");
		
				foreach (uint node in _treeView.GetNodes (args.Contact))
					_treeView.UpdateNode (node);
				
				RefreshTreeView ();
		}
		
		private void SessionContactDisplayMessageChanged (object sender, EntityChangeEventArgs<string> args)
		{
		
				foreach (uint node in _treeView.GetNodes (args.Entity))
					_treeView.UpdateNode (node);
				
				RefreshTreeView ();
		}
		
		private void SessionContactDisplayImageChanged (object sender, EntityChangeEventArgs<IDisplayImage> args)
		{
		
				foreach (uint node in _treeView.GetNodes (args.Entity))
					_treeView.UpdateNode (node);
				
				RefreshTreeView ();
		}
		
		private void SessionContactPresenceChanged (object sender, EntityChangeEventArgs<IPresence> args)
		{
			Console.WriteLine ("$$$$$$$$$$$ PRESENCE CHANGE");
			AimAccount account = Session.Account as AimAccount;
			AimContact contact = args.Entity as AimContact;
			
		
				if (account.ViewByGroup)
				{
					if (args.Old == AimPresence.Offline)
					{
						if (Session.Account.GroupOfflineContacts)
						{
							//the contact was in the virtual 'offline' group -> add to the correct group(s)
							_treeView.RemoveNode (_treeView.GetNodes (_offlineContactsNode, args.Entity)[0]);
						}
						
						AimSession session = Session as AimSession;
						
						foreach (AimGroup group in session.GetGroupsForContact (contact))
						{
							uint[] nodes = _treeView.GetNodes (group);
							
							if (nodes.Length > 0)
								_treeView.AddNode (nodes[0], contact);
							else
								_treeView.AddNode (group); //this will also add the contact
						}
					}
					else if (args.New == AimPresence.Offline)
					{
						if (Session.Account.GroupOfflineContacts)
						{
							//the contact was in a normal group --> add to the offline group
							foreach (uint node in _treeView.GetNodes (contact))
								_treeView.RemoveNode (node);
							
							_treeView.AddNode (_offlineContactsNode, contact);
						}
						else
						{
							foreach (uint node in _treeView.GetNodes (contact))
								_treeView.UpdateNode (node);
						}
					}
					else
					{
						foreach (uint node in _treeView.GetNodes (contact))
							_treeView.UpdateNode (node);
					}
					
					RefreshTreeView ();
				}
				else
				{
					//remove from the offline group and add to the online, or visa versa
					if (args.Old == AimPresence.Offline)
					{
						//if the old presence was offline, it can be a contact that wasn't in the list yet
						uint[] nodes = _treeView.GetNodes (_offlineContactsNode, contact);
						
						if (nodes.Length > 0)
							_treeView.RemoveNode (nodes[0]);
							
						_treeView.AddNode (_onlineContactsNode, contact);
					}
					else
					{
						uint node = _treeView.GetNodes (_onlineContactsNode, contact)[0];
						
						_treeView.RemoveNode (node);
						_treeView.AddNode (_offlineContactsNode, contact);
					}
					
					RefreshTreeView ();
				}
			
			//if (args.Old == AimPresence.Offline)
			//	SoundSetUtility.Play(Sound.ContactSignOn);
			//else if (args.New == AimPresence.Offline)
			//	SoundSetUtility.Play(Sound.ContactSignOff);
		}
		
		private void SessionGroupAdded (object sender, GroupEventArgs args)
		{
			Console.WriteLine ("$$$$$$$$$$$ GROUP ADD");
			AimAccount account = Session.Account as AimAccount;
			if (!account.ShowEmptyGroups && args.Group.OnlineCount == 0)
				return;
			
		
				_treeView.AddNode (args.Group);
				RefreshTreeView ();
			//TODO: keep track of contacts added to the group
		}
		
		private void SessionGroupRemoved (object sender, GroupEventArgs args)
		{
		
				_treeView.RemoveNode (_treeView.GetNodes (args.Group)[0]);
				RefreshTreeView ();
		}
		
		private void SessionGroupRenamed (object sender, GroupEventArgs args)
		{
		
				_treeView.UpdateNode (_treeView.GetNodes (args.Group)[0]);
				RefreshTreeView ();
		}
		
		private void SessionContactAdded (object sender, ContactListEventArgs args)
		{
			Console.WriteLine ("$$$$$$$$$$$ CONTACT ADD");
			AimSession session = Session as AimSession;
			AimAccount account = session.Account as AimAccount;
			
			if (account.ViewByGroup) {
					foreach (AimGroup group in session.GetGroupsForContact (args.Contact as AimContact)) {
						uint[] nodes =  _treeView.GetNodes (group);
						if (nodes.Length == 0) {
							//the group doesn't exist yet (eg: it was empty before)
							_treeView.AddNode (group); //this automatically adds the contact as well
						} else {
							_treeView.AddNode (nodes[0], args.Contact);
						}
					}
					
					RefreshTreeView ();
			} else {
					if (args.Contact.Presence == AimPresence.Online)
						_treeView.AddNode (_onlineContactsNode, args.Contact);
					else
						_treeView.AddNode (_offlineContactsNode, args.Contact);
					RefreshTreeView ();
			}
		}
		
		private void SessionContactReverse (object sender, ContactEventArgs args)
		{
		
		}
		
		private void SessionContactRemoved (object sender, ContactListEventArgs args)
		{
		
				uint child;
				
				if (_treeView.GetFirstNode(_treeView.GetNodes(args.Group)[0], args.Contact, out child))
				{
					_treeView.RemoveNode(child);
					RefreshTreeView ();
				}
		}
		
		private void TreeRowActivated(object sender, RowActivatedArgs args)
		{
			uint node;
			/*
			if (_treeView.GetNodeFromPath(args.Path, out node))
			{
				object obj = _treeView.GetNodeObject(node);
				
				if (obj is AimContact)
				{
					AimContact contact = (AimContact)obj;
					
					// FIXME: Just for debugging.
					
					if (contact.Presence == AimPresence.Offline)
						return;
					
					// See if we have an existing active conversation.
					IConversation conversation = _session.Conversations.GetPrivateConversation(contact);
					
					if (conversation == null)
					{
						// See if we have an existing inactive conversation.
						conversation = _session.Conversations.GetInactiveConversation(contact);
						
						// We are initiating a new conversation, and requesting a switchboard.
						(Session as AimSession).RequestConversation(conversation);
						
						if (conversation == null)
						{
							conversation = new AimConversation(Session, contact);
							
							conversation.DisplayImageReceived += SessionContactDisplayImageChanged;
							
							_session.Conversations.Add(conversation);
						}
					}
					
					if (ActivateChatWidget != null)
						ActivateChatWidget (this, new ChatEventArgs(conversation));
				}
				else if (obj is AimGroup)
				{
					AimGroup group = (AimGroup)obj;
					
					// We may just want to expand/collapse here.
				}
			}*/
		}
		
		[GLib.ConnectBefore ()]
		private void TreeButtonPressed (object sender, ButtonPressEventArgs args)
		{
			TreePath path = null;
			
			if (_treeView.GetPathAtPos ((int)args.Event.X, (int)args.Event.Y, out path))
			{
				uint node;
				
				_treeView.GetNodeFromPath(path, out node);
				
				AimContact contact = _treeView.GetNodeObject(node) as AimContact;
				
				if (contact != null)
					SessionUtility.SelectedContact = contact;
			}
		}
		
		[GLib.ConnectBefore ()]
		private void DisplayNameSetEvent (object sender, KeyPressEventArgs args)
		{
			Gdk.Key key = args.Event.Key;
			uint state = (uint)args.Event.State;
			const uint Normal = 0, Shift = 1, Control = 4, ShiftControl = 5, Alt = 8;
			
			state &= 1101u;
			
			switch (state)
			{
				case Normal:
					//if (args.Event.Key == Gdk.Key.Return || args.Event.Key == Gdk.Key.KP_Enter)
						//_treeView.HasFocus = true;
					break;
			}
		}
		
		private void DisplayNameLeaveEvent (object sender, FocusOutEventArgs args)
		{
			AimAccount account = Session.Account as AimAccount;
			
			if (account.DisplayName.CompareTo(_txtDisplayName.Text) != 0)
			{
				// Send the display name change through the server.
				//(Session as AimSession).SetDisplayName (_txtDisplayName.Text);
			}
		}
		
		private void StatusChangedEvent (object sender, EventArgs args)
		{
			//(Session as AimSession).SetPresenceStatus (_cboStatus.GetSelectedItem ());
		}
		
		private string PresenceTextLookup (IPresence item)
		{
			return item.State;
		}

		private Gdk.Pixbuf PresenceImageLookup (IPresence item)
		{
			return IconUtility.GetIcon (AimPresence.GetIconString (item), IconSizes.Small);
		}
		
		private void AddContactClicked (object sender, EventArgs args)
		{
			
		}
		
		private void EventBoxButtonPressEvent (object sender, ButtonPressEventArgs args)
		{
			
		}
		
		private void ShowNewContactDialog(NewContactActivity activity)
		{
			/*(delegate() {
				string passport = String.Empty;
				
				if (activity.Contact != null)
					passport = activity.Contact.UniqueIdentifier;
				else
					passport = activity.UniqueIdentifier;
				
				NewContactDialog dialog = new NewContactDialog((AimSession)SessionUtility.ActiveSession, passport);
				ResponseType response = ResponseType.None;
				
				while(response == ResponseType.None)
				{
					response = (ResponseType)dialog.Run();
					
					if (response == ResponseType.Ok)
					{
						string error = String.Empty;
						bool success = false;
						
						AimSession session = SessionUtility.ActiveSession as AimSession;
						if (dialog.SelectedGroupID == "0")
							success = session.AddContact (out error, dialog.Passport, dialog.Alias, dialog.Block);
						else
							success = session.AddContactWithGroups (out error, dialog.Passport, dialog.Alias, dialog.Block, dialog.SelectedGroupID);
						
						if (!success)
						{
							MessageDialog errordialog = new MessageDialog (null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, error);
							errordialog.Run ();
							errordialog.Destroy ();
							response = ResponseType.None;
							continue;
						}
						else
						{
							AimContact contact = SessionUtility.ActiveSession.ContactCollection.GetContact(passport) as AimContact;
							
							if (!contact.IsInList(AimListType.Reverse))
								contact.ListType |= AimListType.Reverse;
						}
						
						dialog.Destroy();
						break;
					}
					else
					{
						dialog.Destroy();
						break;
					}
				}
			});*/
		}
		
		// ================================================================== //
		// STATIC FUNCTIONS
		// ================================================================== //
		
		static void OnNewContactActivity(object sender, NewContactActivity activity)
		{
			if (!(activity.Session is AimSession))
				return;
			
			AimSessionWidget widget = AimWidgetUtility.GetSessionWidget ((AimSession)activity.Session);
			widget.ShowNewContactDialog (activity);
		}
	}
}